@@ -102,6 +102,7 @@ $(document).ready -> |
||
102 | 102 |
$("#logs .refresh, #logs .clear").hide() |
103 | 103 |
$.post "/agents/#{agentId}/logs/clear", { "_method": "DELETE" }, (html) => |
104 | 104 |
$("#logs .logs").html html |
105 |
+ $("#show-tabs li a.recent-errors").removeClass 'recent-errors' |
|
105 | 106 |
$("#logs .spinner").stop(true, true).fadeOut -> |
106 | 107 |
$("#logs .refresh, #logs .clear").show() |
107 | 108 |
|
@@ -122,3 +122,7 @@ span.not-applicable:after { |
||
122 | 122 |
margin: 0 10px; |
123 | 123 |
} |
124 | 124 |
} |
125 |
+ |
|
126 |
+#show-tabs li a.recent-errors { |
|
127 |
+ font-weight: bold; |
|
128 |
+} |
@@ -6,7 +6,7 @@ class EventsController < ApplicationController |
||
6 | 6 |
@agent = current_user.agents.find(params[:agent]) |
7 | 7 |
@events = @agent.events.page(params[:page]) |
8 | 8 |
else |
9 |
- @events = current_user.events.page(params[:page]) |
|
9 |
+ @events = current_user.events.preload(:agent).page(params[:page]) |
|
10 | 10 |
end |
11 | 11 |
|
12 | 12 |
respond_to do |format| |
@@ -7,7 +7,7 @@ class LogsController < ApplicationController |
||
7 | 7 |
end |
8 | 8 |
|
9 | 9 |
def clear |
10 |
- @agent.logs.delete_all |
|
10 |
+ @agent.delete_logs! |
|
11 | 11 |
index |
12 | 12 |
end |
13 | 13 |
|
@@ -19,14 +19,6 @@ class Agent < ActiveRecord::Base |
||
19 | 19 |
serialize :options, JSONWithIndifferentAccess |
20 | 20 |
serialize :memory, JSONWithIndifferentAccess |
21 | 21 |
|
22 |
- def options=(o) |
|
23 |
- self[:options] = ActiveSupport::HashWithIndifferentAccess.new(o) |
|
24 |
- end |
|
25 |
- |
|
26 |
- def memory=(o) |
|
27 |
- self[:memory] = ActiveSupport::HashWithIndifferentAccess.new(o) |
|
28 |
- end |
|
29 |
- |
|
30 | 22 |
validates_presence_of :name, :user |
31 | 23 |
validate :sources_are_owned |
32 | 24 |
validate :validate_schedule |
@@ -42,7 +34,6 @@ class Agent < ActiveRecord::Base |
||
42 | 34 |
has_many :events, :dependent => :delete_all, :inverse_of => :agent, :order => "events.id desc" |
43 | 35 |
has_one :most_recent_event, :inverse_of => :agent, :class_name => "Event", :order => "events.id desc" |
44 | 36 |
has_many :logs, :dependent => :delete_all, :inverse_of => :agent, :class_name => "AgentLog", :order => "agent_logs.id desc" |
45 |
- has_one :most_recent_log, :inverse_of => :agent, :class_name => "AgentLog", :order => "agent_logs.id desc" |
|
46 | 37 |
has_many :received_events, :through => :sources, :class_name => "Event", :source => :events, :order => "events.id desc" |
47 | 38 |
has_many :links_as_source, :dependent => :delete_all, :foreign_key => "source_id", :class_name => "Link", :inverse_of => :source |
48 | 39 |
has_many :links_as_receiver, :dependent => :delete_all, :foreign_key => "receiver_id", :class_name => "Link", :inverse_of => :receiver |
@@ -88,13 +79,20 @@ class Agent < ActiveRecord::Base |
||
88 | 79 |
# Implement me in your subclass to test for valid options. |
89 | 80 |
end |
90 | 81 |
|
91 |
- def event_created_within(days) |
|
92 |
- event = most_recent_event |
|
93 |
- event && event.created_at > days.to_i.days.ago && event.payload.present? && event |
|
82 |
+ def options=(o) |
|
83 |
+ self[:options] = ActiveSupport::HashWithIndifferentAccess.new(o) |
|
84 |
+ end |
|
85 |
+ |
|
86 |
+ def memory=(o) |
|
87 |
+ self[:memory] = ActiveSupport::HashWithIndifferentAccess.new(o) |
|
88 |
+ end |
|
89 |
+ |
|
90 |
+ def event_created_within?(days) |
|
91 |
+ last_event_at && last_event_at > days.to_i.days.ago |
|
94 | 92 |
end |
95 | 93 |
|
96 | 94 |
def recent_error_logs? |
97 |
- most_recent_log.try(:level) == 4 |
|
95 |
+ last_event_at && last_error_log_at && last_error_log_at > (last_event_at - 2.minutes) |
|
98 | 96 |
end |
99 | 97 |
|
100 | 98 |
def sources_are_owned |
@@ -134,10 +132,6 @@ class Agent < ActiveRecord::Base |
||
134 | 132 |
self.schedule = nil if cannot_be_scheduled? |
135 | 133 |
end |
136 | 134 |
|
137 |
- def last_event_at |
|
138 |
- @memoized_last_event_at ||= most_recent_event.try(:created_at) |
|
139 |
- end |
|
140 |
- |
|
141 | 135 |
def default_schedule |
142 | 136 |
self.class.default_schedule |
143 | 137 |
end |
@@ -172,6 +166,11 @@ class Agent < ActiveRecord::Base |
||
172 | 166 |
end |
173 | 167 |
end |
174 | 168 |
|
169 |
+ def delete_logs! |
|
170 |
+ logs.delete_all |
|
171 |
+ update_column :last_error_log_at, nil |
|
172 |
+ end |
|
173 |
+ |
|
175 | 174 |
def log(message, options = {}) |
176 | 175 |
puts "Agent##{id}: #{message}" unless Rails.env.test? |
177 | 176 |
AgentLog.log_for_agent(self, message, options) |
@@ -14,6 +14,9 @@ class AgentLog < ActiveRecord::Base |
||
14 | 14 |
oldest_id_to_keep = agent.logs.limit(1).offset(log_length - 1).pluck("agent_logs.id") |
15 | 15 |
agent.logs.where("agent_logs.id < ?", oldest_id_to_keep).delete_all |
16 | 16 |
end |
17 |
+ |
|
18 |
+ agent.update_column :last_error_log_at, Time.now if log.level >= 4 |
|
19 |
+ |
|
17 | 20 |
log |
18 | 21 |
end |
19 | 22 |
|
@@ -40,7 +40,7 @@ module Agents |
||
40 | 40 |
end |
41 | 41 |
|
42 | 42 |
def working? |
43 |
- event_created_within(options['expected_update_period_in_days']) && !recent_error_logs? |
|
43 |
+ event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs? |
|
44 | 44 |
end |
45 | 45 |
|
46 | 46 |
def validate_options |
@@ -26,7 +26,7 @@ module Agents |
||
26 | 26 |
end |
27 | 27 |
|
28 | 28 |
def working? |
29 |
- (event = event_created_within(options['expected_update_period_in_days'])) && event.payload['success'] == true && !recent_error_logs? |
|
29 |
+ event_created_within?(options['expected_update_period_in_days']) && most_recent_event.payload['success'] == true && !recent_error_logs? |
|
30 | 30 |
end |
31 | 31 |
|
32 | 32 |
def default_options |
@@ -62,7 +62,7 @@ module Agents |
||
62 | 62 |
end |
63 | 63 |
|
64 | 64 |
def working? |
65 |
- event_created_within(options['expected_update_period_in_days']) && !recent_error_logs? |
|
65 |
+ event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs? |
|
66 | 66 |
end |
67 | 67 |
|
68 | 68 |
def default_options |
@@ -48,7 +48,7 @@ module Agents |
||
48 | 48 |
end |
49 | 49 |
|
50 | 50 |
def working? |
51 |
- event_created_within(options['expected_update_period_in_days']) && !recent_error_logs? |
|
51 |
+ event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs? |
|
52 | 52 |
end |
53 | 53 |
|
54 | 54 |
def default_options |
@@ -30,7 +30,7 @@ module Agents |
||
30 | 30 |
MD |
31 | 31 |
|
32 | 32 |
def working? |
33 |
- event_created_within(2) && !recent_error_logs? |
|
33 |
+ event_created_within?(2) && !recent_error_logs? |
|
34 | 34 |
end |
35 | 35 |
|
36 | 36 |
def default_options |
@@ -41,7 +41,7 @@ module Agents |
||
41 | 41 |
default_schedule "8pm" |
42 | 42 |
|
43 | 43 |
def working? |
44 |
- event_created_within(2) && !recent_error_logs? |
|
44 |
+ event_created_within?(2) && !recent_error_logs? |
|
45 | 45 |
end |
46 | 46 |
|
47 | 47 |
def wunderground |
@@ -44,7 +44,7 @@ module Agents |
||
44 | 44 |
UNIQUENESS_LOOK_BACK = 30 |
45 | 45 |
|
46 | 46 |
def working? |
47 |
- event_created_within(options['expected_update_period_in_days']) && !recent_error_logs? |
|
47 |
+ event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs? |
|
48 | 48 |
end |
49 | 49 |
|
50 | 50 |
def default_options |
@@ -27,7 +27,7 @@ module Agents |
||
27 | 27 |
end |
28 | 28 |
|
29 | 29 |
def working? |
30 |
- (event = event_created_within(options['expected_update_period_in_days'])) && event.payload['success'] == true && !recent_error_logs? |
|
30 |
+ event_created_within?(options['expected_update_period_in_days']) && most_recent_event.payload['success'] == true && !recent_error_logs? |
|
31 | 31 |
end |
32 | 32 |
|
33 | 33 |
def default_options |
@@ -77,7 +77,7 @@ module Agents |
||
77 | 77 |
end |
78 | 78 |
|
79 | 79 |
def working? |
80 |
- event_created_within(options['expected_update_period_in_days']) && !recent_error_logs? |
|
80 |
+ event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs? |
|
81 | 81 |
end |
82 | 82 |
|
83 | 83 |
def default_options |
@@ -7,18 +7,17 @@ class Event < ActiveRecord::Base |
||
7 | 7 |
|
8 | 8 |
serialize :payload, JSONWithIndifferentAccess |
9 | 9 |
|
10 |
- def payload=(o) |
|
11 |
- self[:payload] = ActiveSupport::HashWithIndifferentAccess.new(o) |
|
12 |
- end |
|
13 |
- |
|
14 |
- |
|
15 | 10 |
belongs_to :user |
16 |
- belongs_to :agent, :counter_cache => true |
|
11 |
+ belongs_to :agent, :counter_cache => true, :touch => :last_event_at |
|
17 | 12 |
|
18 | 13 |
scope :recent, lambda { |timespan = 12.hours.ago| |
19 | 14 |
where("events.created_at > ?", timespan) |
20 | 15 |
} |
21 | 16 |
|
17 |
+ def payload=(o) |
|
18 |
+ self[:payload] = ActiveSupport::HashWithIndifferentAccess.new(o) |
|
19 |
+ end |
|
20 |
+ |
|
22 | 21 |
def reemit! |
23 | 22 |
agent.create_event :payload => payload, :lat => lat, :lng => lng |
24 | 23 |
end |
@@ -11,7 +11,7 @@ |
||
11 | 11 |
<li class='disabled'><a><i class='icon-picture'></i> Summary</a></li> |
12 | 12 |
<li class='active'><a href="#details" data-toggle="tab"><i class='icon-indent-left'></i> Details</a></li> |
13 | 13 |
<% end %> |
14 |
- <li><a href="#logs" data-toggle="tab" data-agent-id="<%= @agent.id %>"><i class='icon-list-alt'></i> Logs</a></li> |
|
14 |
+ <li><a href="#logs" data-toggle="tab" data-agent-id="<%= @agent.id %>" class='<%= @agent.recent_error_logs? ? 'recent-errors' : '' %>'><i class='icon-list-alt'></i> Logs</a></li> |
|
15 | 15 |
|
16 | 16 |
<% if @agent.can_create_events? && @agent.events.count > 0 %> |
17 | 17 |
<li><%= link_to '<i class="icon-random"></i> Events'.html_safe, events_path(:agent => @agent.to_param) %></li> |
@@ -36,7 +36,7 @@ |
||
36 | 36 |
<script> |
37 | 37 |
var agentPaths = {}; |
38 | 38 |
<% if current_user -%> |
39 |
- var myAgents = <%= Utils.jsonify(current_user.agents.inject({}) {|m, a| m[a.name] = agent_path(a) unless a.new_record?; m }) %>; |
|
39 |
+ var myAgents = <%= Utils.jsonify(current_user.agents.select([:name, :id, :schedule]).inject({}) {|m, a| m[a.name] = agent_path(a); m }) %>; |
|
40 | 40 |
$.extend(agentPaths, myAgents); |
41 | 41 |
<% end -%> |
42 | 42 |
agentPaths["All Agents Index"] = <%= Utils.jsonify agents_path %>; |
@@ -0,0 +1,14 @@ |
||
1 |
+class AddCachedDatesToAgent < ActiveRecord::Migration |
|
2 |
+ def up |
|
3 |
+ add_column :agents, :last_event_at, :datetime |
|
4 |
+ execute "UPDATE agents SET last_event_at = (SELECT created_at FROM events WHERE events.agent_id = agents.id ORDER BY id DESC LIMIT 1)" |
|
5 |
+ |
|
6 |
+ add_column :agents, :last_error_log_at, :datetime |
|
7 |
+ execute "UPDATE agents SET last_error_log_at = (SELECT created_at FROM agent_logs WHERE agent_logs.agent_id = agents.id AND agent_logs.level >= 4 ORDER BY id DESC LIMIT 1)" |
|
8 |
+ end |
|
9 |
+ |
|
10 |
+ def down |
|
11 |
+ remove_column :agents, :last_event_at |
|
12 |
+ remove_column :agents, :last_error_log_at |
|
13 |
+ end |
|
14 |
+end |
@@ -19,12 +19,14 @@ describe LogsController do |
||
19 | 19 |
|
20 | 20 |
describe "DELETE clear" do |
21 | 21 |
it "deletes all logs for a specific Agent" do |
22 |
+ agents(:bob_weather_agent).last_error_log_at = 2.hours.ago |
|
22 | 23 |
sign_in users(:bob) |
23 | 24 |
lambda { |
24 | 25 |
delete :clear, :agent_id => agents(:bob_weather_agent).id |
25 | 26 |
}.should change { AgentLog.count }.by(-1 * agents(:bob_weather_agent).logs.count) |
26 | 27 |
assigns(:logs).length.should == 0 |
27 |
- agents(:bob_weather_agent).logs.count.should == 0 |
|
28 |
+ agents(:bob_weather_agent).reload.logs.count.should == 0 |
|
29 |
+ agents(:bob_weather_agent).last_error_log_at.should be_nil |
|
28 | 30 |
end |
29 | 31 |
|
30 | 32 |
it "only deletes logs for an Agent owned by the current user" do |
@@ -67,6 +67,14 @@ describe AgentLog do |
||
67 | 67 |
agents(:jane_website_agent).logs.order("agent_logs.id desc").first.message.should == "message 6" |
68 | 68 |
agents(:jane_website_agent).logs.order("agent_logs.id desc").last.message.should == "message 3" |
69 | 69 |
end |
70 |
+ |
|
71 |
+ it "updates Agents' last_error_log_at when an error is logged" do |
|
72 |
+ AgentLog.log_for_agent(agents(:jane_website_agent), "some message", :level => 3, :outbound_event => events(:jane_website_agent_event)) |
|
73 |
+ agents(:jane_website_agent).reload.last_error_log_at.should be_nil |
|
74 |
+ |
|
75 |
+ AgentLog.log_for_agent(agents(:jane_website_agent), "some message", :level => 4, :outbound_event => events(:jane_website_agent_event)) |
|
76 |
+ agents(:jane_website_agent).reload.last_error_log_at.to_i.should be_within(2).of(Time.now.to_i) |
|
77 |
+ end |
|
70 | 78 |
end |
71 | 79 |
|
72 | 80 |
describe "#log_length" do |
@@ -279,6 +279,32 @@ describe Agent do |
||
279 | 279 |
end |
280 | 280 |
end |
281 | 281 |
|
282 |
+ describe "recent_error_logs?" do |
|
283 |
+ it "returns true if last_error_log_at is near last_event_at" do |
|
284 |
+ agent = Agent.new |
|
285 |
+ |
|
286 |
+ agent.last_error_log_at = 10.minutes.ago |
|
287 |
+ agent.last_event_at = 10.minutes.ago |
|
288 |
+ agent.recent_error_logs?.should be_true |
|
289 |
+ |
|
290 |
+ agent.last_error_log_at = 11.minutes.ago |
|
291 |
+ agent.last_event_at = 10.minutes.ago |
|
292 |
+ agent.recent_error_logs?.should be_true |
|
293 |
+ |
|
294 |
+ agent.last_error_log_at = 5.minutes.ago |
|
295 |
+ agent.last_event_at = 10.minutes.ago |
|
296 |
+ agent.recent_error_logs?.should be_true |
|
297 |
+ |
|
298 |
+ agent.last_error_log_at = 15.minutes.ago |
|
299 |
+ agent.last_event_at = 10.minutes.ago |
|
300 |
+ agent.recent_error_logs?.should be_false |
|
301 |
+ |
|
302 |
+ agent.last_error_log_at = 2.days.ago |
|
303 |
+ agent.last_event_at = 10.minutes.ago |
|
304 |
+ agent.recent_error_logs?.should be_false |
|
305 |
+ end |
|
306 |
+ end |
|
307 |
+ |
|
282 | 308 |
describe "scopes" do |
283 | 309 |
describe "of_type" do |
284 | 310 |
it "should accept classes" do |
@@ -44,18 +44,22 @@ describe Agents::WebsiteAgent do |
||
44 | 44 |
|
45 | 45 |
describe '#working?' do |
46 | 46 |
it 'checks if events have been received within the expected receive period' do |
47 |
+ stubbed_time = Time.now |
|
48 |
+ stub(Time).now { stubbed_time } |
|
49 |
+ |
|
47 | 50 |
@checker.should_not be_working # No events created |
48 | 51 |
@checker.check |
49 | 52 |
@checker.reload.should be_working # Just created events |
50 | 53 |
|
51 | 54 |
@checker.error "oh no!" |
52 |
- @checker.reload.should_not be_working # The most recent log is an error |
|
55 |
+ @checker.reload.should_not be_working # There is a recent error |
|
53 | 56 |
|
54 |
- @checker.log "ok now" |
|
55 |
- @checker.reload.should be_working # The most recent log is no longer an error |
|
57 |
+ stubbed_time = 20.minutes.from_now |
|
58 |
+ @checker.events.delete_all |
|
59 |
+ @checker.check |
|
60 |
+ @checker.reload.should be_working # There is a newer event now |
|
56 | 61 |
|
57 |
- two_days_from_now = 2.days.from_now |
|
58 |
- stub(Time).now { two_days_from_now } |
|
62 |
+ stubbed_time = 2.days.from_now |
|
59 | 63 |
@checker.reload.should_not be_working # Two days have passed without a new event having been created |
60 | 64 |
end |
61 | 65 |
end |